<!DOCTYPE html>
<html>
<head>
<title>04第3章  异步编程和包资源管理.MD</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">

<style>
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

body {
	font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
	font-size: var(--vscode-markdown-font-size, 14px);
	padding: 0 26px;
	line-height: var(--vscode-markdown-line-height, 22px);
	word-wrap: break-word;
}

#code-csp-warning {
	position: fixed;
	top: 0;
	right: 0;
	color: white;
	margin: 16px;
	text-align: center;
	font-size: 12px;
	font-family: sans-serif;
	background-color:#444444;
	cursor: pointer;
	padding: 6px;
	box-shadow: 1px 1px 1px rgba(0,0,0,.25);
}

#code-csp-warning:hover {
	text-decoration: none;
	background-color:#007acc;
	box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}

body.scrollBeyondLastLine {
	margin-bottom: calc(100vh - 22px);
}

body.showEditorSelection .code-line {
	position: relative;
}

body.showEditorSelection .code-active-line:before,
body.showEditorSelection .code-line:hover:before {
	content: "";
	display: block;
	position: absolute;
	top: 0;
	left: -12px;
	height: 100%;
}

body.showEditorSelection li.code-active-line:before,
body.showEditorSelection li.code-line:hover:before {
	left: -30px;
}

.vscode-light.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(0, 0, 0, 0.15);
}

.vscode-light.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(0, 0, 0, 0.40);
}

.vscode-light.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

.vscode-dark.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(255, 255, 255, 0.4);
}

.vscode-dark.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(255, 255, 255, 0.60);
}

.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

.vscode-high-contrast.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(255, 160, 0, 0.7);
}

.vscode-high-contrast.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(255, 160, 0, 1);
}

.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

img {
	max-width: 100%;
	max-height: 100%;
}

a {
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
}

a:focus,
input:focus,
select:focus,
textarea:focus {
	outline: 1px solid -webkit-focus-ring-color;
	outline-offset: -1px;
}

hr {
	border: 0;
	height: 2px;
	border-bottom: 2px solid;
}

h1 {
	padding-bottom: 0.3em;
	line-height: 1.2;
	border-bottom-width: 1px;
	border-bottom-style: solid;
}

h1, h2, h3 {
	font-weight: normal;
}

table {
	border-collapse: collapse;
}

table > thead > tr > th {
	text-align: left;
	border-bottom: 1px solid;
}

table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
	padding: 5px 10px;
}

table > tbody > tr + tr > td {
	border-top: 1px solid;
}

blockquote {
	margin: 0 7px 0 5px;
	padding: 0 16px 0 10px;
	border-left-width: 5px;
	border-left-style: solid;
}

code {
	font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
	font-size: 1em;
	line-height: 1.357em;
}

body.wordWrap pre {
	white-space: pre-wrap;
}

pre:not(.hljs),
pre.hljs code > div {
	padding: 16px;
	border-radius: 3px;
	overflow: auto;
}

pre code {
	color: var(--vscode-editor-foreground);
	tab-size: 4;
}

/** Theming */

.vscode-light pre {
	background-color: rgba(220, 220, 220, 0.4);
}

.vscode-dark pre {
	background-color: rgba(10, 10, 10, 0.4);
}

.vscode-high-contrast pre {
	background-color: rgb(0, 0, 0);
}

.vscode-high-contrast h1 {
	border-color: rgb(0, 0, 0);
}

.vscode-light table > thead > tr > th {
	border-color: rgba(0, 0, 0, 0.69);
}

.vscode-dark table > thead > tr > th {
	border-color: rgba(255, 255, 255, 0.69);
}

.vscode-light h1,
.vscode-light hr,
.vscode-light table > tbody > tr + tr > td {
	border-color: rgba(0, 0, 0, 0.18);
}

.vscode-dark h1,
.vscode-dark hr,
.vscode-dark table > tbody > tr + tr > td {
	border-color: rgba(255, 255, 255, 0.18);
}

</style>

<style>
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */

/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
	color: #8e908c;
}

/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
	color: #c82829;
}

/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
	color: #f5871f;
}

/* Tomorrow Yellow */
.hljs-attribute {
	color: #eab700;
}

/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
	color: #718c00;
}

/* Tomorrow Blue */
.hljs-title,
.hljs-section {
	color: #4271ae;
}

/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
	color: #8959a8;
}

.hljs {
	display: block;
	overflow-x: auto;
	color: #4d4d4c;
	padding: 0.5em;
}

.hljs-emphasis {
	font-style: italic;
}

.hljs-strong {
	font-weight: bold;
}
</style>

<style>
/*
 * Markdown PDF CSS
 */

 body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
	padding: 0 12px;
}

pre {
	background-color: #f8f8f8;
	border: 1px solid #cccccc;
	border-radius: 3px;
	overflow-x: auto;
	white-space: pre-wrap;
	overflow-wrap: break-word;
}

pre:not(.hljs) {
	padding: 23px;
	line-height: 19px;
}

blockquote {
	background: rgba(127, 127, 127, 0.1);
	border-color: rgba(0, 122, 204, 0.5);
}

.emoji {
	height: 1.4em;
}

code {
	font-size: 14px;
	line-height: 19px;
}

/* for inline code */
:not(pre):not(.hljs) > code {
	color: #C9AE75; /* Change the old color so it seems less like an error */
	font-size: inherit;
}

/* Page Break : use <div class="page"/> to insert page break
-------------------------------------------------------- */
.page {
	page-break-after: always;
}

</style>

<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
</head>
<body>
  <script>
    mermaid.initialize({
      startOnLoad: true,
      theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
          ? 'dark'
          : 'default'
    });
  </script>
<h1 id="%E7%AC%AC3%E7%AB%A0-%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E5%92%8C%E5%8C%85%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86">第3章  异步编程和包资源管理</h1>
<ul>
<li><input type="checkbox" id="checkbox0"><label for="checkbox0"></label><strong>学习目标</strong></li>
</ul>
<ol>
<li>理解同步和异步的概念，什么是包和npm</li>
<li>理解回调函数的使用，包的加载规则，npm的使用</li>
</ol>
<h2 id="%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B">异步编程</h2>
<p>​	Javascript语言的执行环境是&quot;单线程&quot;（single thread）。所谓&quot;单线程&quot;，就是指一次只能完成一件任务。如果有多个任务，就必须排队，前面一个任务完成，再执行后面一个任务，以此类推。</p>
<p>​		这种模式的坏处是只要有一个任务耗时很长，后面的任务都必须排队等着，会拖延整个程序的执行。常见的浏览器无响应（假死），往往就是因为某一段Javascript代码长时间运行（比如死循环），导致整个页面卡在这个地方，其他任务无法执行。</p>
<p>​	Javascript语言将任务的执行模式分成两种：同步（Synchronous）和异步（Asynchronous）。</p>
<h3 id="%E5%90%8C%E6%AD%A5%E6%A8%A1%E5%BC%8F%E5%92%8C%E5%BC%82%E6%AD%A5%E6%A8%A1%E5%BC%8F">同步模式和异步模式</h3>
<ol>
<li>同步模式：程序的执行顺序与任务的排列顺序是一致的、同步的；</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// demo3-1.js</span>
<span class="hljs-comment">//同步代码</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'起床'</span>);

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'背单词'</span>);
<span class="hljs-comment">//吃早餐</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">eatBreakfast</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'早餐吃完了'</span>);
}
eatBreakfast();
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'去上学'</span>);
</div></code></pre>
<p>​	运行结果如下图所示，任务代码的顺序与运行结果是一致的。如果代码<code>eatBreakfast()</code>执行时出现了阻塞，那么下一行代码<code>console.log('去上学')</code>就迟迟得不到执行。</p>
<img src="03第3章  异步编程和包资源管理.assets/运行结果1.PNG" alt="运行结果1" style="zoom:50%;" />
<ol start="2">
<li>
<p>异步模式</p>
<ol>
<li>
<p>异步模式需要<strong>回调函数</strong>（callback）的配合完成。回调函数不是由该函数的实现方直接调用，而是在特定的<strong>事件或条件发生时由另外的一方调用</strong>，用于对该事件或条件进行响应。</p>
</li>
<li>
<p>每一个任务有一个或多个回调函数（callback），前一个任务结束后，不是执行后一个任务，而是执行回调函数，后一个任务则是不等前一个任务结束就执行，所以程序的<strong>执行顺序与任务的排列顺序是不一致</strong>的、异步的。</p>
</li>
</ol>
</li>
<li>
<p>异步模式举例1</p>
<p>分析下面代码输出结果。</p>
</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">//异步代码</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-number">1</span>);
setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-number">2</span>);  
}, <span class="hljs-number">100</span>);

setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-number">3</span>);
}, <span class="hljs-number">10</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-number">4</span>);
</div></code></pre>
<p>​	上述代码中含有定时器任务， setTimeout()函数的第一个参数是回调函数，第二个是推迟执行的毫秒数。第一个回调函数如下所示。</p>
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-number">2</span>);
}
</div></code></pre>
<p>​	第二个回调函数如下所示。</p>
<pre class="hljs"><code><div><span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-number">3</span>);
}
</div></code></pre>
<p>​	如果将setTimeout()的第二个参数设为0，就表示<strong>当前代码执行完（执行栈清空）以后</strong>，立即执行（0毫秒间隔）指定的回调函数。所以，下面代码先输出“2”，再输出“1”。因为只有在执行完第二行以后，系统才会去执行&quot;<strong>任务队列</strong>&quot;中的回调函数。</p>
<pre class="hljs"><code><div>setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-built_in">console</span>.log(<span class="hljs-number">1</span>); }, <span class="hljs-number">0</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-number">2</span>);
</div></code></pre>
<p>​	总之，setTimeout(fn,0)的含义是，指定某个任务在主线程最早可得的空闲时间执行，也就是说，尽可能早得执行。它在&quot;任务队列&quot;的尾部添加一个事件，因此要等到同步任务和&quot;任务队列&quot;现有的事件都处理完，才会得到执行。</p>
<p>​	要注意的是，setTimeout()只是<strong>将事件插入了&quot;任务队列&quot;</strong>，必须等到当前代码（执行栈）执行完，主线程才会去执行它指定的回调函数。要是当前代码耗时很长，有可能要等很久，所以并没有办法保证回调函数一定会在setTimeout()指定的时间执行。</p>
<p>​	本例任务执行过程如下图所示。在主线程输出“4”后，开始执行异步任务，先完成定时器任务2，再完成定时器任务1。</p>
<p>![异步模式举例任务执行过程图](03第3章  异步编程和包资源管理.assets/异步模式举例任务执行过程图.png)</p>
<p>(引用：https://segmentfault.com/a/1190000014874668)</p>
<p>​	所以，本例输出的数字顺序为1、4、3、2。</p>
<ol start="4">
<li>
<p>异步模式举例2</p>
<p>文件操作核心模块<code>fs</code>中的<code>readFile</code>方法是一个读取文件内容的方法，它带回调函数作为参数。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// demo3-2-2.js</span>
<span class="hljs-keyword">var</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
fs.readFile(<span class="hljs-string">'input.txt'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err, data</span>) </span>{
    <span class="hljs-keyword">if</span> (err) <span class="hljs-keyword">return</span> <span class="hljs-built_in">console</span>.error(err);
    <span class="hljs-built_in">console</span>.log(data.toString());
});
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"程序执行结束!"</span>);
</div></code></pre>
<p>上述代码执行结果如下图所示。</p>
 <img src="03第3章  异步编程和包资源管理.assets/image-20201015174914498.png" alt="image-20201015174914498" style="zoom: 33%;" />
</li>
</ol>
<h3 id="%E5%BC%82%E5%B8%B8%E6%8D%95%E6%8D%89%E8%AF%AD%E5%8F%A5%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0">异常捕捉语句中使用回调函数</h3>
<ol>
<li>同步模式中处理异常，直接使用try-catch语句</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// demo3-3.js</span>
<span class="hljs-comment">//同步代码处理异常</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseJsonStrToObj</span>(<span class="hljs-params">str</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(str)
}
<span class="hljs-comment">// 对于同步代码，使用 try-catch 来捕获代码执行可能出现的异常</span>
<span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// var data = '{"name":"张三" ,"age": 27}'; </span>
    <span class="hljs-keyword">var</span> data = <span class="hljs-string">'{"name":"张三" ,age: 27}'</span>; <span class="hljs-comment">//age应加上双引号</span>
    <span class="hljs-keyword">var</span> obj=parseJsonStrToObj(data)
    <span class="hljs-built_in">console</span>.log(obj)
} <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'转换失败了'</span>)
}
</div></code></pre>
<ol start="2">
<li>异步代码异常无法使用try-catch</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// demo3-4.js</span>
<span class="hljs-comment">//异步代码无法使用try-catch处理异常。</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseJsonStrToObj</span>(<span class="hljs-params">str</span>) </span>{
    setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(str);
    }, <span class="hljs-number">0</span>);
}
<span class="hljs-comment">//对于异步代码的执行来说，try-catch 是无法捕获异步代码中出现的异常的</span>
<span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">var</span> obj = parseJsonStrToObj(<span class="hljs-string">'foo'</span>);<span class="hljs-comment">//'foo'无法转换为对象</span>
    <span class="hljs-comment">// 上行obj没取得结果，就已经继续向下执行了</span>
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'执行结果是：'</span> + obj);
} <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'转换失败了'</span>);
}
</div></code></pre>
<ol start="3">
<li>异步代码使用try-catch也不能处理异常</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// demo3-5.js</span>
<span class="hljs-comment">//try-catch写在异步代码中 </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseJsonStrToObj</span>(<span class="hljs-params">str</span>) </span>{
    setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(str);
        } <span class="hljs-keyword">catch</span> (e) {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'转换失败了'</span>);
        }
    }, <span class="hljs-number">0</span>);
}
<span class="hljs-comment">//调用方法输出结果</span>
<span class="hljs-keyword">var</span> obj = parseJsonStrToObj(<span class="hljs-string">'foo'</span>);<span class="hljs-comment">//'foo'无法转换为对象</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'执行结果是：'</span> + obj);<span class="hljs-comment">//先输出：执行结果是：undefined</span>
</div></code></pre>
<p>​	执行结果如下图所示。</p>
<img src="03第3章  异步编程和包资源管理.assets/image-20201014122222832.png" alt="image-20201014122222832" style="zoom:67%;" />
<ol start="4">
<li>
<p>通过回调函数接收异步代码执行的处理结果</p>
<p>对于异步代码中出现的异常，需要<strong>使用回调函数</strong>带回异步代码执行中产生的异常。</p>
</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// demo3-6.js</span>
<span class="hljs-comment">//通过回调函数来接收异步代码执行的处理结果。</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseJsonStrToObj</span>(<span class="hljs-params">str, callback</span>) </span>{<span class="hljs-comment">//第二个参数接收回调函数</span>
    setTimeout(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">var</span> obj = <span class="hljs-built_in">JSON</span>.parse(str);<span class="hljs-comment">//转换出现异常</span>
            callback(<span class="hljs-literal">null</span>, obj);
        } <span class="hljs-keyword">catch</span> (e) {
            callback(e, <span class="hljs-literal">null</span>);
        }
    }, <span class="hljs-number">0</span>);
}
<span class="hljs-comment">//下面是函数调用，注意区分错误信息和正确的数据信息</span>
parseJsonStrToObj(<span class="hljs-string">'{foo:"bar"}'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err, result</span>) </span>{<span class="hljs-comment">//foo应该加上双引号</span>
    <span class="hljs-keyword">if</span> (err) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'转换失败了'</span>);
    }
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'数据转换成功，没有问题可以直接使用了：'</span> + result);
});
</div></code></pre>
<h3 id="%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B%E4%B8%AD%E7%9A%84%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%80%9D%E8%B7%AF">异步编程中的“事件驱动”思路</h3>
<p>​	在异步编程中，当主线程异步函数执行时，不确定何时执行完毕，回调函数会被压入到一个事件循环的队列中，然后主线程往下继续执行其它代码，直到异步函数执行完成后，才会开始处理事件循环，调用 相应的回调函数。这个事件循环队列是 一个先进先出的队列，这说明回调是按照它们被加入队列的顺序执行的。</p>
<h2 id="nodejs%E7%9A%84%E5%8C%85%E5%92%8Cnpm">Node.js的包和Npm</h2>
<h3 id="%E5%8C%85%E6%9C%BA%E5%88%B6">包机制</h3>
<p>​	Node.js根据CommonJS规范实现了包机制，开发了npm包管理工具，用来解决包的发布和获取需求。CommonJS模块规范主要分为模块定义、模块标识、模块引用。</p>
<p>​	CommonJS规范规定，每个模块内部，module变量代表当前模块。这个变量是一个对象，它的exports属性（即module.exports）是对外的接口。加载某个模块，其实是加载该模块的module.exports属性。</p>
<p>​	CommonJS模块的特点如下：</p>
<ul>
<li>所有代码都运行在模块作用域，不会污染全局作用域。</li>
<li>模块可以多次加载，但是只会<strong>在第一次加载时运行一次</strong>，然后运行结果就被缓存了，以后再加载，就直接读取缓存结果。要想让模块再次运行，必须清除缓存。</li>
<li>模块加载的顺序，按照其在代码中出现的顺序。</li>
</ul>
<h3 id="%E7%AC%AC%E4%B8%89%E6%96%B9%E6%A8%A1%E5%9D%97%E5%8A%A0%E8%BD%BD%E8%A7%84%E5%88%99">第三方模块加载规则</h3>
<ol>
<li>
<p>引入第三方模块时，先找到当前文件目录下的 node_modules 文件夹，在该目录下寻找与模块名对应的文件夹，通过文件夹中的 package.json 文件的 mian 属性所记录的入口模块，加载、使用第三方包。</p>
</li>
<li>
<p>若 package.js 文件不存在或 main 属性没有指定的入口，则 node 会自动寻找该模块目录下的 index.js 进行加载（默认备选项）。</p>
</li>
<li>
<p>如果以上所有任何一个条件都不成立，则会进入上一级目录中的 node_modules 目录查找，如果上一级还没有，则继续向上一级查找，直到在当前磁盘根目录下还没找到就会报错。</p>
</li>
<li>
<p>模块查找机制：优先从缓存加载。</p>
</li>
</ol>
<h3 id="%E8%A7%84%E8%8C%83%E7%9A%84%E5%8C%85%E7%BB%93%E6%9E%84">规范的包结构</h3>
<p>1.包都要以一个单独的目录而存在；</p>
<p>2.package.json 必须在包的顶层目录下；</p>
<p>3.package.json 文件必须符合 JSON 格式，并且必须包含如下三个属性：name, version, main
name: 包的名字
version: 包的版本号
main: 表示包的入口文件</p>
<p>4.二进制文件应该在bin目录下;</p>
<p>5.javaScript代码应该在lib目录下;</p>
<p>6.文档应该在doc目录下;</p>
<p>7.单元测试应该在test目录下;</p>
<p>8.Node.js对包要求并没有那么严格，只要顶层目录下有package.json，并符合基本规范即可;</p>
<h3 id="%E5%8C%85%E6%8F%8F%E8%BF%B0%E6%96%87%E4%BB%B6-packagejson">包描述文件 package.json</h3>
<p>name：包的名称，必须是唯一
description：包的简要说明
version：符合语义化版本识别规范的版本字符串
keywords：关键字数据，通常用于搜索
maintainers：维护者数组，每个元素要包含name、email、web可选字段
contributors：贡献者数组，格式与maintainers相同。包的坐着应该是贡献者数据的第一个元素
bugs：提交bug的地址，可以是网址或者电子邮件地址
licenses：许可证数组，每个元素要包含type和url字段
repositories：仓库托管地址数组，每个元素要包含type、url和path字段
dependencies：包的依赖，一个关联数组，由包名称和版本号组成。
devDependencies：开发依赖项，表示一个包在开发期间用到的依赖项</p>
<h3 id="npm-%E7%9A%84%E4%B8%A4%E5%B1%82%E5%90%AB%E4%B9%89">npm 的两层含义</h3>
<p>npm 是一个第三方模块的托管网站，指的就是https://www.npmjs.com/；</p>
<p>npm 是Node的包管理工具（全名叫做 Node package manager），在我们安装Node时候，就已经顺便也安装了 npm 这个管理工具；</p>
<h3 id="%E5%AE%89%E8%A3%85%E5%92%8C%E5%8D%B8%E8%BD%BD%E5%85%A8%E5%B1%80%E5%8C%85"><strong>安装和卸载全局包</strong></h3>
<p>1.什么是全局的包：安装到计算机全局环境中的包，叫做全局包；安装的全局包可以在当前电脑的任何目录下，直接通过命令行来访问；</p>
<p>2.如何安装全局包：运行 npm install 包名 -g 即可；其中 -g 参数，表示 把包安装到全局目录中的意思；</p>
<p>3.全局包的安装目录：C:\Users\用户目录\AppData\Roaming\npm</p>
<p>4.什么样的包适合安装到全局：工具性质的包，适合安装到全局；</p>
<p>5.如何卸载全局包：要卸载某个全局的包，直接运行npm uninstall 包名 -g即可；其中 uninstall 表示卸载的意思；</p>
<h3 id="%E5%AE%89%E8%A3%85%E5%92%8C%E5%8D%B8%E8%BD%BD%E6%9C%AC%E5%9C%B0%E5%8C%85-%E5%90%8E%E8%B7%9F%E7%89%88%E6%9C%AC%E5%8F%B7"><strong>安装和卸载本地包 @后跟版本号</strong></h3>
<p>npm init -y 初始化package.json</p>
<p>npm -v 查询npm版本号</p>
<p>1.什么是本地的包：跟着项目安装的包，叫做本地包；本地包都会被安装到 node_modules 目录下；</p>
<p>2.注意：如果拿到一个空项目，必须在当前项目根目录中，先运行 npm init或者npm init -y 命令，初始化一个package.json的配置文件，否则包无法安装到本地项目中；</p>
<p>3.如何安装本地包：运行npm i 包名 --save 即可安装本地包；都安装到了当前项目的 node_modules目录下；</p>
<p>4.如果大家用的是npm 5.x的版本，可以不指定–save命令，如果用的是 npm 3.x 的版本，则需要手动指定 --save；</p>
<p>5.package-lock.json文件中记录了曾经装过的包的下载地址，方便下次直接下载包，能够加快装包的速度，提升装包的体验；</p>
<p>6.如何卸载本地包：使用 npm uninstall/remove 包名 -S/-D即可卸载指定的本地包；</p>
<h3 id="%E5%85%B6%E5%AE%83%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4"><strong>其它常用命令</strong></h3>
<p>1.–save 的缩写是 -S</p>
<p>2.–save-dev的缩写是 -D</p>
<p>3.install 的缩写是 i</p>
<p>4.注意：dependencies 节点，表示项目上线部署时候需要的依赖项；devDependencies节点，表示项目在开发阶段需要的依赖项，但是当项目要部署上线了，devDependencies节点中的包，就不再需要了。</p>
<p>5.注意：当使用 npm i快速装包的时候，npm会检查package.json文件中，所有的依赖项，然后都为我们安装到项目中。
6.–production 表示只安装 dependencies 节点下，记录的包，不安装devDependencies节点下的包；当项目要上线了，才会使用–production命令。</p>
<h3 id="%E8%A7%A3%E5%86%B3-npm-%E4%B8%8B%E8%BD%BD%E6%85%A2%E9%97%AE%E9%A2%98"><strong>解决 npm 下载慢问题</strong></h3>
<p>1.默认，npm在下载包的时候，连接的是国外的服务器，所以，有时候如果网速不是特别好，可能下载不下来包，此时，可以全局安装一个的工具，叫做 cnpm。</p>
<p>2.如何安装cnpm：运行 npm i cnpm -g 即可；</p>
<p>3.如何使用cnpm：在装包的时候，只需要把 npm 替换成 cnpm 即可，例如：
使用 npm 安装 jquery：运行 npm i jquery -S
使用 cnpm 安装 jquery： 运行 cnpm i jquery -S
把 npm 下载切换到淘宝 npm config set registry <a href="https://registry.npm.taobao/">https://registry.npm.taobao</a></p>
<h3 id="npm%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B">npm工具使用举例</h3>
<p>​	下载第三包，包名为markdown。使用这个包，可以实现自己的markdown 网页编辑器。要求把项目代码传至码云仓库。</p>
<ol>
<li>
<p>登录码云（https://gitee.com/），新建“test-markdown”仓库；</p>
</li>
<li>
<p>复制“test-markdown”仓库地址，如下图所示；</p>
<img src="03第3章  异步编程和包资源管理.assets/clone仓库.PNG" alt="clone仓库" style="zoom:50%;" />
</li>
<li>
<p>建立目录“D:\Course\code\chapter03”；</p>
</li>
<li>
<p>在“chapter03&quot;目录下，右键菜单命令”Git Bash Here“；</p>
</li>
<li>
<p>克隆仓库到本地。在命令窗口中输入”git clone &quot;，再粘贴上前面复制下来的HTTPS地址（ctrl+右键菜单），如下所示；</p>
</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-variable">$</span> git clone https://gitee.com/wzynbx/<span class="hljs-built_in">test-markdown</span>.git
</div></code></pre>
<p>​	经克隆，本地有了“test-markdown”文件夹，且该文件夹中生成了“.git”文件夹。</p>
<ol start="6">
<li>可以继续在命令窗口中，使用如下命令打开vscode。如果code命令启动不了vscode，用其它方法打开项目文件夹；</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-variable">$</span> cd <span class="hljs-built_in">test-markdown</span>
<span class="hljs-variable">$</span> code .
</div></code></pre>
<ol start="7">
<li>在vscode的终端中生成包描述文件 package.json；</li>
</ol>
<pre class="hljs"><code><div>D:\Course\code\chapter03\<span class="hljs-built_in">test-markdown</span>&gt; npm init <span class="hljs-literal">-y</span>
</div></code></pre>
<ol start="8">
<li>本地安装markdown包，经安装后，项目文件夹如下图所示；</li>
</ol>
<pre class="hljs"><code><div>npm i markdown
</div></code></pre>
<img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构.PNG" alt="项目文件夹结构" style="zoom:50%;" />
<p>​	由上图可以，markdown包安装后，项目文件夹就有了”node_modules“文件夹，并在该文件夹中用”markdown“文件夹存放下载的包。</p>
<ol start="9">
<li>在项目文件夹下新建”lib“文件夹，在该文件夹下新建”demo.js“文件，在该文件中引用”markdown“包；</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-keyword">var</span> markdown = <span class="hljs-built_in">require</span>(<span class="hljs-string">"markdown"</span>).markdown;
<span class="hljs-built_in">console</span>.log(markdown.toHTML(<span class="hljs-string">"Hello *World*!"</span>));
</div></code></pre>
<p>​	此时，项目目录结构如下图所示。</p>
<img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构2.PNG" alt="项目文件夹结构2" style="zoom:50%;" />
<ol start="10">
<li>以上图中的”lib“文件夹为当前目录，执行”demo.js“文件代码，要理解为何上述代码为何能正确引用”markdown“包。</li>
</ol>
<pre class="hljs"><code><div>D:\Course\code\chapter03\<span class="hljs-built_in">test-markdown</span>\lib&gt; node .\demo.js
&lt;p&gt;Hello &lt;em&gt;World&lt;/em&gt;!&lt;/p&gt;
</div></code></pre>
<p>​	require(&quot;markdown&quot;)参数是不带路径的名称，所以”markdown“被认定为第三方包；当前目录下没有”node_modules“ 文件夹，进入上一级目录中的 ”node_modules“ 目录查找与&quot;markdown&quot;同名的文件夹，显然查找成功，在&quot;markdown&quot;文件夹的”package.json“中指出包的入口。</p>
<pre class="hljs"><code><div><span class="hljs-comment">//...</span>
<span class="hljs-string">"main"</span>: <span class="hljs-string">"./lib/index.js"</span>,
<span class="hljs-comment">//...</span>
</div></code></pre>
<p>​	而上面代码中的index.js代码如下所示。因此执行index.js引用了同文件夹下的”markdown.js“，并向外暴露了”markdown“对象。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// super simple module for the most common nodejs use case.</span>
exports.markdown = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./markdown"</span>);
exports.parse = exports.markdown.toHTML;
</div></code></pre>
<ol start="11">
<li>将开发文档和配置保存至码云仓库；</li>
</ol>
<p>因为没必要上传”node_modules“文件夹，及其它不必要的文档，所以一般都要在项目文件夹在新建”.gitignore“文件，指出哪些资源无需上传至仓库。”.gitignore“文件内容各异，可自行设置。</p>
<img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构3.PNG" alt="项目文件夹结构3" style="zoom:50%;" />
<pre class="hljs"><code><div>node_modules
.git
.msi
.exe
</div></code></pre>
<ol start="12">
<li>使用命令窗口提交，这是基于本地项目文件夹是从仓库克隆的，内有”.git“文件夹。</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-variable">$</span> git add .
<span class="hljs-variable">$</span>  git commit <span class="hljs-literal">-m</span> <span class="hljs-string">'commit时的消息'</span>
<span class="hljs-variable">$</span> git push
</div></code></pre>
<ol start="13">
<li>在vscode界面提交，这是基于本地项目文件夹是从仓库克隆的，内有”.git“文件夹。</li>
</ol>
<p><img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构4.PNG" alt="项目文件夹结构4" style="zoom:50%;" /><img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构5.PNG" alt="项目文件夹结构5" style="zoom:50%;" /><img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构6.PNG" alt="项目文件夹结构6" style="zoom:50%;" /><img src="03第3章  异步编程和包资源管理.assets/项目文件夹结构7.PNG" alt="项目文件夹结构7" style="zoom:50%;" /><img src="03第3章  异步编程和包资源管理.assets/同步更改-1599990077816.PNG" alt="同步更改" style="zoom:50%;" /></p>
<h3 id="npm%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E5%B0%8F%E7%BB%93">npm常用命令小结</h3>
<p>​	npm命令功能丰富，命令写法多种多样，下表列出一些常见用法。</p>
<table>
<thead>
<tr>
<th>npm命令</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>npm init</td>
<td>生成package.json 文件</td>
</tr>
<tr>
<td>npm init -y</td>
<td>快速生成package.json 文件，跳过向导</td>
</tr>
<tr>
<td>npm install 模块</td>
<td>下载模块到本项目，package.json 中记录依赖项</td>
</tr>
<tr>
<td>npm install</td>
<td>一次性全部安装package.json 中的dependencies 依赖项</td>
</tr>
<tr>
<td>npm install --save 模块<br />npm i -S 模块</td>
<td>下载模块，并将依赖项记录到package.json 中的dependencies节点</td>
</tr>
<tr>
<td>npm install -g 模块</td>
<td>全局安装模块，安装的模块不在本项目，而是：<br />“C:\Users\Administrator\AppData\Roaming\npm”，其中盘符和用户名不一样</td>
</tr>
<tr>
<td>npm uninstall 模块</td>
<td>删除模块，但不删除模块留在package.json中dependencies下的对应信息</td>
</tr>
<tr>
<td><strong>npm uninstall 模块 --save</strong></td>
<td>删除模块，同时删除模块留在package.json中dependencies下的对应信息</td>
</tr>
<tr>
<td>npm uninstall -g 包名</td>
<td>删除指定名称的全局模块</td>
</tr>
<tr>
<td><strong>npm uninstall 模块 --save-dev</strong></td>
<td>删除模块，同时删除模块留在package.json中devDependencies下的对应信息</td>
</tr>
<tr>
<td>npm config list</td>
<td>查看npm基本配置，不是全部配置，其中有npm的源地址</td>
</tr>
<tr>
<td>npm config set registry http://registry.npm.taobao.org/</td>
<td>设为淘宝镜像</td>
</tr>
<tr>
<td>npm config set registry http://www.npmjs.org</td>
<td>换回npm官方仓库源</td>
</tr>
</tbody>
</table>
<h2 id="%E5%B0%8F%E7%BB%93">小结</h2>
<p>​	为了避免单线程阻塞，Node.js中加入异步编程模块。异步无法使用try...catch处理异常，此种情形可以使用回 调函数接收异步代码的执行结果。异步函数执行时，不确定完毕时间，回调函数会被压入一个队列，然后接着往下执行其他代码，直到异步函数执行完成后，才会调用相应的回调函数。</p>
<p>​	Node.js中，一个包相当于封装了几个模块，而包中会有一个出口模块用于与外界进行关联。并且，Node.js还针对包的管理，设计了一个包管理工具npm。包模块作为一种特殊的文件模块遵循require()包加载规则。</p>
<p>​	回调函数和包的管理在Node.js中是非常常见的操作。</p>

</body>
</html>
